//
//  Backend.swift
//  Automobile
//
//  Created by erkebulan elzhan on 7/31/20.
//  Copyright © 2020 erkebulan elzhan. All rights reserved.


import Foundation



struct Request {
    let limit: Int?
    let offset: Int
    let parameters: [URLQueryItem]
    var base: String!
    
    enum Service: String {
        case advert = "advert.test.auto-nomad.kz"
        case garage = "garage.test.auto-nomad.kz"
    }
    
    func getUrl (path: String) -> URL {
        var components = URLComponents()
        components.scheme = "http"
        components.host = base
        components.port = 8000
        components.path = "/api/" + path
        if parameters.count != 0 {
            components.queryItems = parameters
        }
        return components.url!
    }
    
    init (base: Service = .advert,
          limit: Int?=nil, offset: Int=0,
          addOffset: Bool = false,
          parameters: [URLQueryItem]?=nil) {
        
        self.base = base.rawValue
        self.limit = limit
        self.offset = offset
        var commonParameters:[URLQueryItem] = []
        
        if addOffset {
            commonParameters = [
                URLQueryItem(name: "offset", value: "\(offset)")
            ]
        }
        
        if limit != nil { commonParameters.append(URLQueryItem(name: "limit", value: "\(limit!)")) }
        self.parameters = commonParameters + (parameters ?? [])
    }
}


enum RequestError: Error {
    case general
    case parameter (String)
}


class Backend {
    
    func getData<T: Decodable> (for: T.Type = T.self,
                                request: URLRequest,
                                completion: @escaping (Result<T, RequestError>) -> Void) {
        
        var authRequest = request
        authRequest.allHTTPHeaderFields = ["authorization": User.shared.token, "Content-type": "application/json"]
        
        let dataTask = URLSession.shared.dataTask(with: authRequest) { data, _, _ in
           guard let jsonData = data else {
              return completion(.failure(.general))
           }
            
           do {
                let decoder = JSONDecoder()
                let response = try decoder.decode(T.self, from: jsonData)
                completion(.success(response))
           } catch {
                completion(.failure(.parameter(self.returnError(data: jsonData))))
           }
       }
       dataTask.resume()
    }

    func fetchFromBackend<T:Decodable> (for: T.Type = T.self, request: URLRequest,
                          statusCode: Int,
                          sendBool: Bool = false,
                          sendBody: Bool = false,
                          defaultHeaders: Bool = true,
                          body: [String:Any?] = [:],
                          completion: @escaping (Result<T, RequestError>) -> Void) {
        
        var authRequest = request
        
        if defaultHeaders {
            authRequest.allHTTPHeaderFields = ["Authorization": User.shared.token, "Content-type": "application/json"]
        } else {
            authRequest.setValue(User.shared.token, forHTTPHeaderField: "Authorization")
        }
        
        if sendBody, let postData = try? JSONSerialization.data(withJSONObject: body, options: []) {
            authRequest.httpBody = postData
        }
        
        let dataTask = URLSession.shared.dataTask(with: authRequest) { data, response, _ in
            
            DispatchQueue.main.async {
                guard let jsonData = data else {
                    completion(.failure(.general))
                    return
                }
                
                if let response = response as? HTTPURLResponse, let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) {
                    do {
                        if response.statusCode == statusCode {
                            if (sendBool) {
                                 completion(.success(true as! T))
                            } else {
                                let decoder = JSONDecoder()
                                let jsonResponse = try decoder.decode(T.self, from: jsonData)
                                completion(.success(jsonResponse))
                            }
                            
                        } else {
                            completion(.failure(.parameter(self.returnError(data: json))))
                        }
                    } catch {
                        completion(.failure(.parameter(self.returnError(data: json))))
                    }
                } else {
                    completion(.failure(.parameter(self.returnError(data: jsonData))))
                }
            }
        }
        dataTask.resume()
    }
}



extension Backend {
    
    public func addToFavorites (advertID: Int, isRemove: Bool=false, completion: @escaping (Bool, String?) -> Void) {
        let headers = [ "content-type": "application/json", "authorization": User.shared.token ]
        
        if (isRemove) {
            self.removeFromFavourite(headers: headers, advertId: advertID, completion: completion)
        } else {
            self.newFavourite(headers: headers, advertID: advertID, completion: completion)
        }
    }
    
    private func returnError (data: Any) -> String {
        if data is NSDictionary {
          return String(describing: data)
        } else if data is Data {
          if let json = try? JSONSerialization.jsonObject(with: data as! Data, options: []) {
            return String(describing: json)
          } else {
            return "Неизвестная ошибка"
          }
        } else {
            print(String(describing: data))
            return String(describing: data)
        }
    }
    
    // Adding/Removing Favourite
    
    private func newFavourite(headers: [String: String], advertID: Int, completion: @escaping (Bool, String?) -> Void) {
        let url = Request(base: .advert).getUrl(path: "adverts/my/favourites/")
        let bodyData = ["advert": advertID]
        
        if let postData = try? JSONSerialization.data(withJSONObject: bodyData, options: []) {
            var request = URLRequest(url: url)
            request.httpBody = postData
            request.httpMethod = "POST"
            request.allHTTPHeaderFields = headers
            self.fetchFromBackend(for: Bool.self, request: request, statusCode: 201, sendBool: true) { (result) in
                switch result {
                case .failure(let error):
                    switch error {
                    case .general:
                        completion(true, "Неизвестная ошибка")
                    case .parameter(let msg):
                        completion(true, msg)
                    }
                case .success(_):
                    completion(true, nil)
                }
            }
        }
    }
    
    private func removeFromFavourite (headers: [String: String], advertId: Int, completion: @escaping (Bool, String?) -> Void) {
        let url = Request(base: .advert).getUrl(path: "adverts/my/favourites/\(advertId)/")
        var request = URLRequest(url: url)
        request.httpMethod = "DELETE"
        request.allHTTPHeaderFields = headers
        self.fetchFromBackend(for: Bool.self, request: request, statusCode: 200, sendBool: true) { (result) in
            switch result {
            case .failure(let error):
                switch error {
                case .general:
                    completion(true, "Неизвестная ошибка")
                case .parameter(let msg):
                    completion(true, msg)
                }
            case .success(_):
                completion(true, nil)
            }
        }
    }
}

